﻿using ETWManifest;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

internal class TraceParserGen
{
    public TraceParserGen(Provider provider)
    {
        m_provider = provider;

        // group the events by event Name.
        m_eventsByName = new SortedDictionary<string, List<Event>>();
        foreach (var evnt in m_provider.Events)
        {
            List<Event> eventsForName = GetEventsForName(evnt.EventName, evnt.Id);
            eventsForName.Add(evnt);
        }
    }

    /// <summary>
    /// Given an eventName, return a list of all events with that name.   Warns if two events
    /// with the same name have different Ids, and retnames the event to be unique.
    /// </summary>
    /// <param name="eventName"></param>
    /// <param name="eventId"></param>
    /// <returns></returns>
    private List<Event> GetEventsForName(string eventName, ushort eventId)
    {
        List<Event> eventsForName;
        if (!m_eventsByName.TryGetValue(eventName, out eventsForName))
        {
            m_eventsByName[eventName] = eventsForName = new List<Event>();
        }

        // Make sure that every event has a unique names, Warn if this is not true and morph name by adding the EventId as a suffix.
        foreach (var eventWithName in eventsForName)
        {
            if (eventWithName.Id != eventId)
            {
                var newName = eventName + eventId.ToString();
                Console.WriteLine("Error: events with ID {0} and {1} have the same name (same opcode name and task name) renaming to {2} fix.",
                    eventWithName.Id, eventId, newName);
                // TODO should we rename or simply leave it?   Not clear yet... For now we rename
                return GetEventsForName(newName, eventId);
            }
        }
        return eventsForName;
    }

    /* Once you have created a TraceParserGen, you can set various options using the properties below */
    /// <summary>
    /// This is the prefix for any class names.  Users can override this, but by default
    /// it is the last component (components separated by -) of the provider name.  Users
    /// can override this however.
    /// </summary>
    public string ClassNamePrefix
    {
        get
        {
            if (m_ClassNamePrefix == null)
            {
                m_ClassNamePrefix = TraceParserGen.ToCSharpName(m_provider.Name);
                // We use the last component of the - separated list.
                int lastDash = m_ClassNamePrefix.LastIndexOf('-');
                if (lastDash > 0)
                {
                    m_ClassNamePrefix = m_ClassNamePrefix.Substring(lastDash + 1);
                }
            }
            return m_ClassNamePrefix;
        }
        set
        {
            m_ClassNamePrefix = value;
        }
    }
    /// <summary>
    /// If set then it assumes that the generated class should be internal and not public.
    /// </summary>
    public bool Internal;
    /// <summary>
    /// If true it will cause the generation of a 'state' class associated with the parser to hold information needed from one event to the next
    /// </summary>
    public bool NeedsParserState;

    /// <summary>
    /// Once you have set all the properties you wish, you can actually geneate a TraceEventParser to a
    /// particular output file by calling this routine.
    /// </summary>
    public void GenerateTraceEventParserFile(string outputFileName)
    {
        using (var output = File.CreateText(outputFileName))
        {
            output.WriteLine("//<autogenerated/>");
            output.WriteLine("using System;");
            output.WriteLine("using System.Diagnostics;");
            output.WriteLine("using System.Diagnostics.Tracing;");
            output.WriteLine("using System.Text;");
            output.WriteLine("using Microsoft.Diagnostics.Tracing;");
            output.WriteLine("using Address = System.UInt64;");
            output.WriteLine();
            output.WriteLine("#pragma warning disable 1591        // disable warnings on XML comments not being present");
            output.WriteLine("");
            output.WriteLine("// This code was automatically generated by the TraceParserGen tool, which converts");
            output.WriteLine("// an ETW event manifest into strongly typed C# classes.");
            GenerateTraceEventParserClass(output);
        }
    }

    #region private

    /* Methods that implement the main spine of the C# code generation */
    private void GenerateTraceEventParserClass(TextWriter output)
    {
        output.WriteLine("namespace Microsoft.Diagnostics.Tracing.Parsers");
        output.WriteLine("{");
        output.WriteLine("    using Microsoft.Diagnostics.Tracing.Parsers.{0};", ClassNamePrefix);
        output.WriteLine("");

        string stateClassName = ClassNamePrefix + "State";

        output.WriteLine("    [System.CodeDom.Compiler.GeneratedCode(\"traceparsergen\", \"2.0\")]");
        output.WriteLine("    public sealed class " + ClassNamePrefix + "TraceEventParser : TraceEventParser ");
        output.WriteLine("    {");
        output.WriteLine("        public static string ProviderName = \"" + m_provider.Name + "\";");
        output.WriteLine("        public static Guid ProviderGuid = " + CodeForGuidLiteral(m_provider.Id) + ";");
        GenerateKeywords(output);
        output.WriteLine("        public " + ClassNamePrefix + "TraceEventParser(TraceEventSource source) : base(source) {}");
        output.WriteLine("");

        // *********** GENERATE EVENTS ********** //
        GenerateEvents(output);
        output.WriteLine("");
        output.WriteLine("        #region private");
        output.WriteLine("        protected override string GetProviderName() { return ProviderName; }");
        output.WriteLine();

        // *********** GENERATE TEMPLATE DEFINTITIONS ********** //
        GenerateTemplateDefs(output);

        if (NeedsParserState)
        {
            output.WriteLine("        private " + stateClassName + " State");
            output.WriteLine("        {");
            output.WriteLine("            get");
            output.WriteLine("            {");
            output.WriteLine("                " + stateClassName + " ret = (" + stateClassName + ") StateObject;");
            output.WriteLine("                if (ret == null) ");
            output.WriteLine("                {");
            output.WriteLine("                    ret = new " + stateClassName + "();");
            output.WriteLine("                    ParserState = ret;");
            output.WriteLine("                }");
            output.WriteLine("                return ret;");
            output.WriteLine("            }");
            output.WriteLine("        }");
            output.WriteLine("        " + stateClassName + " state;");
        }
        output.WriteLine("        #endregion");
        output.WriteLine("    }");
        if (NeedsParserState)
        {
            output.WriteLine("    #region private types");
            output.WriteLine("    internal class " + stateClassName + " : IFastSerializable");
            output.WriteLine("    {");
            output.WriteLine("        //TODO: Fill in");
            output.WriteLine("        void IFastSerializable.ToStream(Serializer serializer)");
            output.WriteLine("        {");
            output.WriteLine("        }");
            output.WriteLine("        void IFastSerializable.FromStream(Deserializer deserializer)");
            output.WriteLine("        {");
            output.WriteLine("        }");
            output.WriteLine("    }");
            output.WriteLine("    #endregion");
        }
        output.WriteLine("}");
        output.WriteLine("");

        output.WriteLine("namespace Microsoft.Diagnostics.Tracing.Parsers.{0}", ClassNamePrefix);
        output.WriteLine("{");

        // *********** GENERATE CLASSES ********** //
        GenerateEventPayloadClass(output, stateClassName);

        GenerateEnumerations(output);

        output.WriteLine("}");
    }

    private void GenerateKeywords(TextWriter output)
    {
        output.WriteLine("        public enum Keywords : long");
        output.WriteLine("        {");
        ulong keyword = 1;
        for (int i = 0; i < 64; i++)
        {
            string name = TraceParserGen.ToCSharpName(m_provider.GetKeywordName(i));
            if (name != null)
            {
                name = FixKeywordName(name);
                output.WriteLine("            {0} = 0x{1:x},", name, keyword);
            }
            keyword = keyword << 1;
        }
        output.WriteLine("        };");
        output.WriteLine("");
    }

    /// <summary>
    /// Change convention from ETW_KEYWORD_SESSION" to Session
    /// </summary>
    private static string FixKeywordName(string keywordName)
    {
        int keywordIdx = keywordName.IndexOf("KEYWORD_");
        if (0 <= keywordIdx)
        {
            keywordName = keywordName.Substring(keywordIdx + 8);
        }

        // Convert it to CamelCase.

        var sb = new StringBuilder();
        bool capitalizeNext = true;
        for (int i = 0; i < keywordName.Length; i++)
        {
            char c = keywordName[i];
            if (c == '_')
            {
                capitalizeNext = true;
            }
            else
            {
                if (capitalizeNext)
                {
                    c = Char.ToUpperInvariant(c);
                }
                else
                {
                    c = Char.ToLowerInvariant(c);
                }

                sb.Append(c);
                capitalizeNext = false;
            }
        }
        return sb.ToString();
    }

    /// <summary>
    /// Generate the *Template helper functions as well as the EnumerateTemplates operation.
    /// </summary>
    /// <param name="output"></param>
    private void GenerateTemplateDefs(TextWriter output)
    {

        // Geneate all the helper functions that initialize a single template for an event.
        foreach (var keyValue in m_eventsByName)
        {
            var evntName = keyValue.Key;
            var evnt = keyValue.Value[0];
            string templateClassName = GetTemplateNameForEvent(evnt, evntName);
            var stateArg = "";
            if (NeedsParserState)
            {
                stateArg = ", " + templateClassName + "State state=null";
            }

            output.WriteLine("        static private {0} {1}Template(Action<{0}> action{2})", templateClassName, TraceParserGen.ToCSharpName(evntName), stateArg);
            output.WriteLine("        {                  // action, eventid, taskid, taskName, taskGuid, opcode, opcodeName, providerGuid, providerName");
            var state = "";
            if (NeedsParserState && templateClassName != "EmptyTraceData")
            {
                state = ", state";
            }

            output.WriteLine("            return new {0}(action, {1}, {2}, \"{3}\", Guid.Empty, {4}, \"{5}\", ProviderGuid, ProviderName {6});",
                templateClassName, evnt.Id, evnt.Task, TraceParserGen.ToCSharpName(evnt.TaskName), evnt.Opcode, TraceParserGen.ToCSharpName(evnt.OpcodeName), state);
            output.WriteLine("        }");
        }

        var internalOpt = "";
        if (Internal)
        {
            internalOpt = "internal ";
        }

        output.WriteLine();
        output.WriteLine("        static private volatile TraceEvent[] s_templates;");
        output.WriteLine("        protected {0}override void EnumerateTemplates(Func<string, string, EventFilterResponse> eventsToObserve, Action<TraceEvent> callback)", internalOpt);
        output.WriteLine("        {");
        output.WriteLine("            if (s_templates == null)");
        output.WriteLine("            {");
        output.WriteLine("                var templates = new TraceEvent[{0}];", m_provider.Events.Count);
        for (int i = 0; i < m_provider.Events.Count; i++)
        {
            var evnt = m_provider.Events[i];

            // check if the same event template has not been already defined (different versions of the same event)
            output.WriteLine("                templates[{0}] = new {1}TraceData(null, {2}, {3}, \"{4}\", {4}TaskGuid, {5}, \"{6}\", ProviderGuid, ProviderName);",
                                              i, TraceParserGen.ToCSharpName(evnt.EventName),
                                              evnt.Id, evnt.Task, TraceParserGen.ToCSharpName(evnt.TaskName), evnt.Opcode, TraceParserGen.ToCSharpName(evnt.OpcodeName)
                                              );
            // as of today, the generated code won't compile because the task GUID is not defined
            // TODO: define the xxxTaskGuid based on eventGUID attribute of <task> elements of the .man file
        }

        output.WriteLine("                s_templates = templates;");
        output.WriteLine("            }");
        output.WriteLine("            foreach (var template in s_templates)");
        output.WriteLine("                if (eventsToObserve == null || eventsToObserve(template.ProviderName, template.EventName) == EventFilterResponse.AcceptEvent)");
        output.WriteLine("                    callback(template);");
        output.WriteLine("        }");
        output.WriteLine();
    }

    /// <summary>
    /// Emit the C# events that allow you to get callback
    /// </summary>
    private void GenerateEvents(TextWriter output)
    {
        foreach (var keyValue in m_eventsByName)
        {
            var evntName = keyValue.Key;
            evntName = Regex.Replace(evntName, "[^a-zA-Z0-9_]+", "", RegexOptions.Compiled);
            Debug.Assert(0 < keyValue.Value.Count);
            var evnt = keyValue.Value[0];

            string templateClassName = GetTemplateNameForEvent(evnt, evntName);
            // need to handle empty event names because there is no task property in the .man file
            // --> this is the case for event 270 | EventSource
            if (string.IsNullOrEmpty(evntName))
            {
                evntName = evnt.Symbol + "Event";
            }

            output.WriteLine("        public event Action<" + templateClassName + "> " + evntName);
            output.WriteLine("        {");
            output.WriteLine("            add");
            output.WriteLine("            {");
            var taskGuid = (string.IsNullOrEmpty(evnt.TaskName))
                ? "Guid.Empty"
                : evnt.TaskName + "TaskGuid";
            var taskName = TraceParserGen.ToCSharpName(evnt.TaskName);
            if (string.IsNullOrEmpty(taskName)) taskName = evntName;
            // Call the *Template() function that does the work
            output.WriteLine("                RegisterTemplate(new {0}(value, {1}, {2}, \"{3}\", {4}, {5}, \"{6}\", ProviderGuid, ProviderName));",
                                              templateClassName, evnt.Id, evnt.Task, taskName, taskGuid,
                                              evnt.Opcode, TraceParserGen.ToCSharpName(evnt.OpcodeName)
                                              );
            output.WriteLine("            }");
            output.WriteLine("            remove");
            output.WriteLine("            {");
            output.WriteLine("                source.UnregisterEventTemplate(value, " + evnt.Id + ", " + taskGuid + ");");
            output.WriteLine("            }");
            output.WriteLine("        }");
        }
    }

    private void GenerateEventPayloadClass(TextWriter output, string stateClassName)
    {
        // Severla distinct events might have the same payload class, this Dictionary keeps track
        // of which we have emitted so we don't emit it twice.
        var classesEmitted = new Dictionary<string, string>();

        // For every event of the same name
        foreach (var keyValue in m_eventsByName)
        {
            var eventName = keyValue.Key;
            var versionsForEvent = keyValue.Value;

            // We have to accumulate all the information needed to fetch a field, across all the versions of the event
            // Only then can we emit the code that will work for all fields simultaneously.
            var fieldVersions = new SortedDictionary<string, List<FieldInfo>>();
            // allFields is the accumulation of all the values in 'fieldsVersions'
            List<FieldInfo> allFields = new List<FieldInfo>();
            string lengthAssert = "";       // Ultimately it is a expression which is true about the payload length
            GetFieldInfoByField(versionsForEvent, fieldVersions, allFields, ref lengthAssert);

            if (allFields.Count <= 0)
            {
                continue;
            }

            // This is the name of the template we will be generating.
            string templateClassName = GetTemplateNameForEvent(versionsForEvent[0], eventName);

            // Have we done it already?
            if (classesEmitted.ContainsKey(templateClassName))
            {
                continue;
            }

            classesEmitted.Add(templateClassName, null);

            // OK we are ready to write it all out.
            output.WriteLine("    public sealed class " + templateClassName + " : TraceEvent");
            output.WriteLine("    {");

            // Write out all the getters.
            foreach (FieldInfo fieldInfo in allFields)
            {
                string name = fieldInfo.Name;
                string safeName = "";

                if (!reservedKeywords.TryGetValue(fieldInfo.Name, out safeName))
                {
                    safeName = fieldInfo.Name;
                }

                if (SkipPadOrReservedFields(fieldInfo))
                {
                    output.WriteLine("        // Skipping " + fieldInfo.Name);
                }
                else
                {
                    // Take cases on struct array, struct, value array, value.
                    if (fieldInfo.GetType() == typeof(StructInfo))
                    {
                        // Struct or struct array.  Iterate through all fields contained
                        // in the struct

                        // TODO: Multiple version support not available yet for struct fields
                        StructInfo structInfo = (StructInfo)fieldInfo;

                        foreach (FieldInfo structSubfieldInfo in structInfo.StructSubfieldInfos)
                        {
                            if (structInfo.VarSizedArrayCountPropertyName == null)
                            {
                                // struct (not array):
                                // The getter for each struct field looks just like a
                                // regular field getter, except the field name is
                                // prepended with the struct name
                                output.WriteLine("        public " + structSubfieldInfo.Type + " " + safeName + "_" + structSubfieldInfo.Name + "{ get { return " + structSubfieldInfo.Fetch() + "; } }");

                                // TODO: Arrays contained inside structs not supported
                            }
                            else
                            {
                                // var-sized array of an entire structure.  Create a
                                // method that takes an array index as parameter

                                // TODO: Fixed-sized arrays of structs not yet supported
                                // by traceparsergen
                                int dummy;
                                Debug.Assert(!int.TryParse(structInfo.VarSizedArrayCountPropertyName, out dummy));

                                output.WriteLine("        public " + structSubfieldInfo.Type + " " + safeName + "_" + structSubfieldInfo.Name + "(int arrayIndex) { return " + structSubfieldInfo.Fetch() + "; }");
                            }
                        }
                    }
                    else
                    {
                        // Not a struct field.  Just a regular field or array of fields

                        List<FieldInfo> versions = fieldVersions[name];

                        if (fieldInfo.VarSizedArrayCountPropertyName == null)
                        {
                            output.WriteLine("        public " + GetType(versions) + " " + safeName + " { get { " + GetPropertyStmt(versions, versionsForEvent.Count) + " } }");
                        }
                        else if (fieldInfo.Type == "byte[]")
                        {
                            // for 'byte[]' we generate two accessors: the first allocates a new byte[], the second allows indexed access
                            output.WriteLine("        public " + GetType(versions) + " " + safeName + " { get { " + GetPropertyStmt(versions, versionsForEvent.Count) + " } }");
                        }
                        else
                        {
                            // Var-sized non-blob arrays are accessed not via properties, but
                            // methods that take an array index as parameter.
                            // (Note:  Perhaps this should be done for fixed-sized
                            // arrays as well?)
                            output.WriteLine("        public " + GetType(versions) + " " + safeName + "(int arrayIndex) { " + GetPropertyStmt(versions, versionsForEvent.Count) + " }");
                        }
                    }
                }
            }

            output.WriteLine("");
            output.WriteLine("        #region Private");

            // Write out the constructor
            output.Write("        internal " + templateClassName + "(Action<" + templateClassName + "> action, int eventID, int task, string taskName, Guid taskGuid, int opcode, string opcodeName, Guid providerGuid, string providerName");
            if (NeedsParserState)
            {
                output.Write(", " + ClassNamePrefix + "State state");
            }

            output.WriteLine(")");
            output.WriteLine("            : base(eventID, task, taskName, taskGuid, opcode, opcodeName, providerGuid, providerName)");
            output.WriteLine("        {");
            output.WriteLine("            Action = action;");
            if (NeedsParserState)
            {
                output.WriteLine("            this.m_state = state;");
            }

            output.WriteLine("        }");

            var internalOpt = "";
            if (Internal)
            {
                internalOpt = "internal ";
            }

            // Write out the dispatch method
            output.WriteLine("        protected {0}override void Dispatch()", internalOpt);
            output.WriteLine("        {");
            output.WriteLine("            Action(this);");
            output.WriteLine("        }");

            // And the debugging logic
            output.WriteLine("        protected {0}override void Validate()", internalOpt);
            output.WriteLine("        {");
            output.Write(lengthAssert);
            output.WriteLine("        }");

            // And for setting and inspecting the callback delegate
            output.WriteLine("        protected {0}override Delegate Target", internalOpt);
            output.WriteLine("        {");
            output.WriteLine("            get { return Action; }");
            output.WriteLine("            set { Action = (Action<" + templateClassName + ">) value; }");
            output.WriteLine("        }");

            // Write out a 'ToXml' that has all the fields
            output.WriteLine("        public override StringBuilder ToXml(StringBuilder sb)");
            output.WriteLine("        {");
            output.WriteLine("             Prefix(sb);");
            foreach (FieldInfo fieldInfo in allFields)
            {
                string safeName = "";

                if (!reservedKeywords.TryGetValue(fieldInfo.Name, out safeName))
                {
                    safeName = fieldInfo.Name;
                }

                if (!SkipPadOrReservedFields(fieldInfo) && (fieldInfo.GetType() != typeof(StructInfo)) && (fieldInfo.VarSizedArrayCountPropertyName == null))
                {
                    string printFtn = "XmlAttrib";
                    if (fieldInfo.Type == "Address" || fieldInfo.HexFormat)
                    {
                        printFtn = "XmlAttribHex";
                    }

                    output.WriteLine("             " + printFtn + "(sb, \"" + safeName + "\", " + safeName + ");");
                }
            }
            output.WriteLine("             sb.Append(\"/>\");");
            output.WriteLine("             return sb;");
            output.WriteLine("        }");
            output.WriteLine("");

            // Write out the PayLoadNames method
            output.WriteLine("        public override string[] PayloadNames");
            output.WriteLine("        {");
            output.WriteLine("            get");
            output.WriteLine("            {");
            output.WriteLine("                if (payloadNames == null)");
            output.Write("                    payloadNames = new string[] { ");
            bool first = true;
            foreach (FieldInfo fieldInfo in allFields)
            {
                string safeName = "";

                if (!reservedKeywords.TryGetValue(fieldInfo.Name, out safeName))
                {
                    safeName = fieldInfo.Name;
                }
                if (!SkipPadOrReservedFields(fieldInfo) && (fieldInfo.GetType() != typeof(StructInfo)))
                {
                    if (!first)
                    {
                        output.Write(", ");
                    }

                    output.Write("\"" + safeName + "\"");
                    first = false;
                }
            }
            output.WriteLine("};");
            output.WriteLine("                return payloadNames;");
            output.WriteLine("            }");
            output.WriteLine("        }");
            output.WriteLine("");

            // Write out the PayloadValue method
            output.WriteLine("        public override object PayloadValue(int index)");
            output.WriteLine("        {");
            output.WriteLine("            switch (index)");
            output.WriteLine("            {");
            int fieldNum = 0;
            foreach (FieldInfo fieldInfo in allFields)
            {
                string safeName = "";

                if (!reservedKeywords.TryGetValue(fieldInfo.Name, out safeName))
                {
                    safeName = fieldInfo.Name;
                }
                if (!SkipPadOrReservedFields(fieldInfo) && (fieldInfo.GetType() != typeof(StructInfo)) && (fieldInfo.VarSizedArrayCountPropertyName == null))
                {
                    output.WriteLine("                case " + fieldNum + ":");
                    output.WriteLine("                    return " + safeName + ";");
                    fieldNum++;
                }
            }
            output.WriteLine("                default:");
            output.WriteLine("                    Debug.Assert(false, \"Bad field index\");");
            output.WriteLine("                    return null;");
            output.WriteLine("            }");
            output.WriteLine("        }");
            output.WriteLine("");

            // Write out the keywords belonging to this template
            output.WriteLine("        public static ulong GetKeywords() { return " + versionsForEvent[0].Keywords + "; }");
            output.WriteLine("        public static string GetProviderName() { return \"" + versionsForEvent[0].Provider.Name + "\"; }");
            output.WriteLine("        public static Guid GetProviderGuid() { return new Guid(\"" + versionsForEvent[0].Provider.Id + "\"); }");

            // Write out the fields specific to this TraceEvent (that are not payload)
            output.WriteLine("        private event Action<" + templateClassName + "> Action;");
            if (NeedsParserState)
            {
                output.WriteLine("        protected internal override void SetState(object newState) { m_state = (" + ClassNamePrefix + "State)newState; }");
                output.WriteLine("        private " + ClassNamePrefix + "State m_state;");
            }
            output.WriteLine("        #endregion");
            output.WriteLine("    }");
        }

    }

    /// <summary>
    /// Accumulate all the information needed to fetch a field by field name.  Also compute the assert that
    /// we will use to confirm the payload length is good.
    /// </summary>
    private void GetFieldInfoByField(List<Event> versionsForEvent, SortedDictionary<string, List<FieldInfo>> fieldVersions, List<FieldInfo> allFields, ref string lengthAssert)
    {
        int curVersion = 0;
        foreach (Event eventVersion in versionsForEvent)
        {
            if (eventVersion.Fields != null)
            {
                // Figure out the field offsets for the fields for this version.
                FieldInfo lastField = null;
                foreach (FieldInfo fieldInfo in GetInfoForFields(eventVersion.Fields))
                {
                    List<FieldInfo> versionsOfField;
                    if (!fieldVersions.TryGetValue(fieldInfo.Name, out versionsOfField))
                    {
                        versionsOfField = new List<FieldInfo>();
                        fieldVersions.Add(fieldInfo.Name, versionsOfField);
                        allFields.Add(fieldInfo);
                    }
                    versionsOfField.Add(fieldInfo);
                    fieldInfo.VersionNum = eventVersion.Version;
                    fieldInfo.EventId = eventVersion.Id;
                    lastField = fieldInfo;
                }
                if (lastField != null)
                {
                    lengthAssert += "            Debug.Assert(!(Version == " + eventVersion.Version + " && EventDataLength != " + FieldInfo.Skip(lastField) + "));\r\n";
                    if (curVersion == versionsForEvent.Count - 1)
                    {
                        lengthAssert += "            Debug.Assert(!(Version > " + eventVersion.Version + " && EventDataLength < " + FieldInfo.Skip(lastField) + "));\r\n";
                    }
                }
            }
            curVersion++;
        }
    }

    /// <summary>
    /// Generate all the enumeration types needed by the class defintions.
    /// </summary>
    /// <param name="output"></param>
    private void GenerateEnumerations(TextWriter output)
    {
        // We also keep track of every enumerated type we use so we can emit those defintions too
        var enumerationsUsed = new SortedDictionary<string, Enumeration>();

        // For every event of the same name
        foreach (List<Event> versionsForEvent in m_eventsByName.Values)
        {
            foreach (var eventVersion in versionsForEvent)
            {
                // see what enumerations we use.
                if (eventVersion.Fields != null)
                {
                    foreach (Field field in eventVersion.Fields)
                    {
                        if (field.Enumeration != null)
                        {
                            enumerationsUsed[TraceParserGen.ToCSharpName(field.Enumeration.Name)] = field.Enumeration;
                        }
                    }
                }
            }
        }

        // Emit all the enumerations that were used in the payload defintions.
        foreach (var enumeration in enumerationsUsed.Values)
        {
            GenerateEventEnumeration(enumeration, output);
        }
    }

    private void GenerateEventEnumeration(Enumeration enumeration, TextWriter output)
    {
        if (enumeration.IsBitField)
        {
            output.WriteLine("    [Flags]");
        }

        // remove the Map suffix (more like .NET conventions)
        string name = EnumerationName(enumeration);

        output.WriteLine("    public enum {0}", name);
        output.WriteLine("    {");

        foreach (var keyValue in enumeration.Values)
        {
            string numericString = Convert.ToString(keyValue.Key, 16);
            Int64 targetNumber = Convert.ToInt64(numericString, 16);

            if (targetNumber >= Int32.MaxValue)
            {
                output.WriteLine("        {0} = unchecked((int)  0x{1:x}),", keyValue.Value, keyValue.Key);
            }
            else
            {
                output.WriteLine("        {0} = 0x{1:x},", keyValue.Value, keyValue.Key);
            }
        }
        output.WriteLine("    }");
    }

    private static string EnumerationName(Enumeration enumeration)
    {
        string name = TraceParserGen.ToCSharpName(enumeration.Name);
        if (name.EndsWith("Map"))
        {
            name = name.Substring(0, name.Length - 3);
        }

        if (name.EndsWith("_Value", StringComparison.OrdinalIgnoreCase))
        {
            name = name.Substring(0, name.Length - 6);
        }

        return name;
    }

    /* More support methods */
    /// <summary>
    /// returns C# code that will generate 'guid' in an efficient way (initializing by string is
    /// inefficient).
    /// </summary>
    private static string CodeForGuidLiteral(Guid guid)
    {
        StringBuilder sb = new StringBuilder();
        sb.Append("new Guid(");
        byte[] bytes = guid.ToByteArray();
        // Silverlight does not have the unsigned versions for the GUID.  Uggh.  OK force to the signed version
        sb.Append("unchecked((int) 0x");
        sb.Append(bytes[3].ToString("x").PadLeft(2, '0'));
        sb.Append(bytes[2].ToString("x").PadLeft(2, '0'));
        sb.Append(bytes[1].ToString("x").PadLeft(2, '0'));
        sb.Append(bytes[0].ToString("x").PadLeft(2, '0'));
        sb.Append("), unchecked((short) 0x");
        sb.Append(bytes[5].ToString("x").PadLeft(2, '0'));
        sb.Append(bytes[4].ToString("x").PadLeft(2, '0'));
        sb.Append("), unchecked((short) 0x");
        sb.Append(bytes[7].ToString("x").PadLeft(2, '0'));
        sb.Append(bytes[6].ToString("x").PadLeft(2, '0'));
        sb.Append(")");
        for (int i = 8; i < 16; i++)
        {
            sb.Append(", 0x" + bytes[i].ToString("x").PadLeft(2, '0'));
        }

        sb.Append(")");
        return sb.ToString();
    }
    private static bool SkipPadOrReservedFields(FieldInfo fieldInfo)
    {
        if (fieldInfo.Name == null)
        {
            return true;
        }

        if (fieldInfo.Type == null)
        {
            return true;
        }

        if (fieldInfo.Name.StartsWith("Reserved", StringComparison.OrdinalIgnoreCase))
        {
            if (fieldInfo.Name.Length == 8 || Char.IsDigit(fieldInfo.Name[8]))
            {
                return true;
            }
        }
        if (fieldInfo.Name.StartsWith("Pad", StringComparison.OrdinalIgnoreCase))
        {
            if (fieldInfo.Name.Length == 3 || Char.IsDigit(fieldInfo.Name[3]))
            {
                return true;
            }
        }
        return false;
    }
    private static string GetTemplateNameForEvent(Event evnt, string eventName)
    {
        var ret = "EmptyTraceData";
        if (evnt.Fields != null && evnt.Fields.Count > 0)
        {
            ret = TraceParserGen.ToCSharpName(evnt.TemplateName);
            if (ret == null || ret.StartsWith("tid_"))
            {
                ret = TraceParserGen.ToCSharpName(eventName) + "Args";
            }
            else
            {
                ret = ret + "TraceData";
            }
        }
        return ret;
    }

    /// <summary>
    /// Find all the information needed to create a payload decode class for a template (including offsets
    /// of the fields) and returns it as a list of 'FieldInfo' structures.
    /// </summary>
    private List<FieldInfo> GetInfoForFields(IList<Field> fields)
    {
        FieldInfo prevFieldInfo = null;
        List<FieldInfo> ret = new List<FieldInfo>();

        foreach (Field field in fields)
        {
            string varSizedArray = null;
            int count = 1;
            if (field.CountField != null)
            {
                if (!int.TryParse(field.CountField, out count))
                {
                    count = 1;
                    varSizedArray = field.CountField;
                }
            }

            FieldInfo fieldInfo;
            if (field.Struct != null)
            {
                fieldInfo = new StructInfo(TraceParserGen.ToCSharpName(field.Name));
            }
            else
            {
                fieldInfo = new FieldInfo(TraceParserGen.ToCSharpName(field.Name), field.Type, prevFieldInfo, count, varSizedArray);
            }

            fieldInfo.PrevField = prevFieldInfo;
            ret.Add(fieldInfo);
            fieldInfo.HexFormat = field.HexFormat;

            if (field.Struct != null)
            {
                StructInfo structInfo = (StructInfo)fieldInfo;

                // A <struct> field will have its own <data> child elements, so call
                // self recursively to generate FieldInfos for all of them.
                List<FieldInfo> structSubfieldInfos = GetInfoForFields(field.Struct.Fields);

                // Calculate size/offset info about the struct
                int cbStruct = 0;
                int cPointersInStruct = 0;
                foreach (FieldInfo structSubfieldInfo in structSubfieldInfos)
                {
                    cbStruct += structSubfieldInfo.ByteSize;
                    cPointersInStruct += structSubfieldInfo.NumPointersInSize;

                    // Point each field back up to its struct
                    structSubfieldInfo.ContainingStructInfo = structInfo;
                }

                fieldInfo.Type = "<Type unused for structs>";
                fieldInfo.FetchMethod = "<FetchMethod unused for structs>";
                fieldInfo.ByteSize = cbStruct;
                fieldInfo.NumPointersInSize = cPointersInStruct;

                // Point struct to its fields
                structInfo.StructSubfieldInfos = structSubfieldInfos;
                continue;
            }

            if (field.Enumeration != null)
            {
                fieldInfo.Type = EnumerationName(field.Enumeration);
                fieldInfo.IsEnum = true;
            }
            fieldInfo.ByteSize *= count;
            prevFieldInfo = fieldInfo;
        }
        return ret;
    }
    /// <summary>
    /// Returns a string representing the C# code that will fetch the property for any of the versions described by 'versions'.
    /// 'totalVersions' is the maximum number of versions that the event (not just the property) has.
    /// </summary>
    private string GetPropertyStmt(List<FieldInfo> versions, int totalVersions)
    {
        Debug.Assert(versions.Count > 0);

        // The expression for fetching the previous version of this field.  Null is fine as it
        // does not match any legit fetch expression string.
        string prevFetch = null;

        // Represents a statement that will compute the value of all versions up to the i'th one
        string curStmt;
        // If this field exists in all versions or the first version is version 0 (which means there
        // can not be any previous versions, we start out with the smallest version fetch
        // Otherwise we start out with null value (not present value).
        if (versions.Count == totalVersions || versions[0].VersionNum == 0)
        {
            prevFetch = versions[0].Fetch();
            curStmt = "return " + prevFetch + ";";
        }
        else
        {
            curStmt = "return " + versions[0].NullValue() + ";";
        }

        // Versions are in ordered from lowest number to highest.
        foreach (FieldInfo version in versions)
        {
            string fetch = version.Fetch();
            if (fetch != prevFetch)
            {
                Debug.Assert(version.VersionNum != 0);
                curStmt = "if (Version >= " + version.VersionNum + ") return " + fetch + "; " + curStmt;
                prevFetch = fetch;
            }
        }
        return curStmt;
    }
    private string GetType(List<FieldInfo> versions)
    {
        string ret = null;
        foreach (FieldInfo version in versions)
        {
            if (ret == null)
            {
                ret = version.Type;
            }

            ret = MergeType(ret, version.Type, false);
        }
        return ret;
    }

    /// <summary>
    /// ensures that the name is a valid CSharp Identifier.
    /// </summary>
    public static string ToCSharpName(string input)
    {
        if (input == null)
        {
            return null;
        }

        // Note: the previous implementation did not seem right
        // by removing characters while iterating based on the string length
        var validName = new StringBuilder(input.Length);
        for (int i = 0; i < input.Length; i++)
        {
            char c = input[i];
            if (!Char.IsLetter(c) && !Char.IsDigit(c) && c != '_')
            {
                // skip this character
                continue;
            }

            validName.Append(c);
        }
        return validName.ToString();
    }

    private string MergeType(string type1, string type2, bool triedSwap)
    {
        if (type1 == type2)
        {
            return type1;
        }

        if (type1 == "long" && type2 == "int")
        {
            return "long";
        }

        if (type1 == "long" && type2 == "Address")
        {
            return "long";
        }

        if (type1 == "Address" && type2 == "int")
        {
            return "Address";
        }

        if (triedSwap == false)
        {
            return MergeType(type2, type1, true);
        }

        Console.WriteLine("Error: Incompatible types " + type1 + " and " + type2 + " look for BAD_MERGE_OF in output for more.");
        return "BAD_MERGE_OF_" + type1 + "_AND_" + type2;
    }

    /// <summary>
    /// Represents all the information needed to decode a specific version of a field of a event payload.
    /// </summary>
    private class FieldInfo
    {
        protected FieldInfo() { }
        public FieldInfo(string name, string typeName, FieldInfo prevField, int count, string varSizedArray)
        {
            VarSizedArrayCountPropertyName = varSizedArray;
            PrevField = prevField;

            Name = name;
            switch (typeName)
            {
                case "win:Pointer":
                case "trace:SizeT":
                    Type = "Address";
                    FetchMethod = "GetAddressAt";
                    NumPointersInSize = 1;
                    ByteSize = 4;
                    break;
                // Booleans are DWORD sized.
                case "win:Boolean":
                    Type = "bool";
                    FetchMethod = "GetInt32At";
                    ByteSize = 4;
                    break;
                case "win:UInt8":
                case "win:HexInt8":
                case "win:Int8":
                    Type = "int";
                    FetchMethod = "GetByteAt";
                    ByteSize = 1;
                    break;
                case "win:UInt16":
                case "win:HexInt16":
                case "win:Int16":
                case "trace:Port":
                    Type = "int";
                    FetchMethod = "GetInt16At";
                    ByteSize = 2;
                    break;
                // TODO do we want to support unsigned?
                case "win:UInt32":
                case "win:HexInt32":
                case "win:Int32":
                case "trace:IPAddr":
                case "trace:IPAddrV4":
                    Type = "int";
                    FetchMethod = "GetInt32At";
                    ByteSize = 4;
                    break;
                case "win:Double":
                    Type = "double";
                    FetchMethod = "GetDoubleAt";
                    ByteSize = 8;
                    break;
                case "win:Float":
                    Type = "float";
                    FetchMethod = "GetSingleAt";
                    ByteSize = 4;
                    break;
                case "trace:WmiTime":
                case "win:HexInt64":
                case "win:UInt64":
                case "win:Int64":
                    Type = "long";
                    FetchMethod = "GetInt64At";
                    ByteSize = 8;
                    break;
                case "trace:UnicodeChar":
                    Type = "string";
                    FetchMethod = "GetFixedUnicodeStringAt(" + count + ", ";

                    // TODO add a fixed size string parser routine.
                    ByteSize = 2;
                    break;
                case "win:UnicodeString":
                    Type = "string";
                    FetchMethod = "GetUnicodeStringAt";
                    SkipMethod = "SkipUnicodeString(" + FieldInfo.Skip(prevField) + ")";
                    break;
                case "win:AnsiString":
                    Type = "string";
                    FetchMethod = "GetUTF8StringAt";
                    SkipMethod = "SkipUTF8String(" + FieldInfo.Skip(prevField) + ")";
                    break;
                case "trace:IPAddrV6":
                    // TODO deal with this.
                    ByteSize = 16;
                    break;
                case "trace:WBEMSid":
                    SkipMethod = "SkipSID(" + FieldInfo.Skip(prevField) + ")";
                    break;
                case "win:GUID":
                case "trace:Guid":
                    Type = "Guid";
                    FetchMethod = "GetGuidAt";
                    ByteSize = 16;
                    break;
                case "win:FILETIME":
                    Type = "DateTime";
                    FetchMethod = "DateTime.FromFileTime(GetInt64At";
                    ByteSize = 8;
                    break;
                case "win:Binary":
                    Type = "byte[]";
                    FetchMethod = "GetByteArrayAt";
                    ByteSize = 1;
                    break;
                default:
                    Debug.Assert(false, "Unknown type " + typeName);
                    break;
            }
        }

        public string Name;
        public string Type;
        public bool HexFormat;
        public bool IsEnum;
        public string FetchMethod;

        // Size in bytes of this field. If this field is a statically sized array, ByteSize
        // is the entire size of the array. If this field is a variable sized array (whose
        // size is determined at run-time by using the value of another field in the ETW
        // event), then ByteSize remains the size of each entry of the array.
        public int ByteSize;

        public int NumPointersInSize;
        public FieldInfo PrevField;
        public string SkipMethod;           // If the field is variable sized, this method can skip it.
        public int VersionNum;
        public int EventId;

        // If this field resides inside a struct, this points to that struct (else this is null)
        public StructInfo ContainingStructInfo;

        // Name of property that returns the count, if this is a variable sized array (else null)
        public string VarSizedArrayCountPropertyName;

        /// <summary>
        /// Returns string representing code that returns a 'default' value (a value to return when the value
        /// does not exist in the payload. This is 0 for numeric values and null for string values.
        /// </summary>
        /// <returns></returns>
        public string NullValue()
        {
            if (Type == "string")
            {
                return "\"\"";
            }

            if (IsEnum)
            {
                return "(" + Type + ")0";
            }

            return "0";
        }

        // Typically, this gives a string representation of the offset of this field
        // (in bytes, relative to beginning of the containing event).  However, if this
        // field exists within a struct, then this gives the offset of this field relative
        // to the beginning of that struct
        public string Offset()
        {
            if (PrevField == null)
            {
                return "0";
            }

            return FieldInfo.Skip(PrevField);
        }
        /// <summary>
        /// Returns a string representing the offset of whatever is directly after this field.
        /// This method is static so that it can be called on a null field, which is convenient in several places.
        /// </summary>
        /// <returns></returns>
        public static string Skip(FieldInfo field)
        {
            string loc = "0";
            int numPointers = 0;
            int numBytes = 0;

            // Go through the previous fields, adding up their offsets.
            FieldInfo ptr = field;
            while (ptr != null)
            {
                if (ptr.SkipMethod != null)
                {
                    loc = ptr.SkipMethod;
                    break;
                }
                if (ptr.VarSizedArrayCountPropertyName == null)
                {
                    numBytes += ptr.ByteSize;
                }
                else
                {
                    // Var-sized arrays are skipped by multiplying the value of the property
                    // that returns the array count by the number of bytes per array entry
                    loc += "+ (" + ptr.VarSizedArrayCountPropertyName + "*" + ptr.ByteSize.ToString() + ")";
                }

                numPointers += ptr.NumPointersInSize;
                ptr = ptr.PrevField;
            }
            if (loc == "0")
            {
                loc = numBytes.ToString();
            }
            else if (numBytes > 0)
            {
                loc = loc + "+" + numBytes.ToString();
            }

            if (numPointers != 0)
            {
                return "HostOffset(" + loc + ", " + numPointers + ")";
            }

            return loc;
        }

        // Depending on the kind of field we're talking about, the fetch string will look
        // quite different:
        //
        // 1) Typical case (non-array, non-struct value):
        //
        //     FetchMethod(<OffsetToThisFieldFromEventStart>)
        //
        // 2) Var-sized array of non-struct value:
        //
        //     FetchMethod(<OffsetToThisFieldFromEventStart> + (arrayIndex * <SizeOfThisFieldInBytes>))
        //
        // 3) This field resides in a single, non-array struct
        //
        //     FetchMethod(<OffsetToTheContainingStructFromEventStart> + <OffsetToThisFieldFromStructStart>)
        //
        // 4) This field resides in a var-sized array of structs
        //
        //     FetchMethod(<OffsetToTheContainingStructFromEventStart> + (arrayIndex * <SizeOfContainingStructInBytes>) + <OffsetToThisFieldFromStructStart>)
        //
        public string Fetch()
        {
            string ret = FetchMethod + "(";
            if (ContainingStructInfo == null)
            {
                if (VarSizedArrayCountPropertyName == null)
                {
                    // Non-struct, non-varsized array
                    //
                    // FetchMethod(<OffsetToThisFieldFromEventStart>)
                    ret += Offset() + ")";
                }
                else if (Type == "byte[]")
                {
                    ret += Offset() + ", " + VarSizedArrayCountPropertyName + ")";
                }
                else
                {
                    // For Var-sized arrays of single (non-struct) values, after we get the
                    // offset to the beginning of the array, add in the extra offset to get
                    // to the specified arrayIndex
                    //
                    // FetchMethod(<OffsetToThisFieldFromEventStart> + (arrayIndex * <SizeOfThisFieldInBytes>))
                    // TODO: I don't think this works on x64 for arrays of pointers
                    ret += Offset() + " + (arrayIndex * HostOffset(" + ByteSize.ToString() + ", " + NumPointersInSize.ToString() + ")))";
                }
            }
            else
            {
                if (ContainingStructInfo.VarSizedArrayCountPropertyName == null)
                {
                    // Single Struct (non-array)
                    //
                    // FetchMethod(<OffsetToTheContainingStructFromEventStart> + <OffsetToThisFieldFromStructStart>)
                    ret += ContainingStructInfo.Offset() + " + " + Offset() + ")";
                }
                else
                {
                    // For fields residing in var-sized arrays of structs, we start with the
                    // offset to the containing struct, then skip struct entries in the
                    // array, and then finally add in the offset to this field relative to
                    // the start of the struct
                    //
                    // FetchMethod(<OffsetToTheContainingStructFromEventStart> + (arrayIndex * <SizeOfContainingStructInBytes>) + <OffsetToThisFieldFromStructStart>)
                    ret += ContainingStructInfo.Offset() + " + (arrayIndex * HostOffset(" + ContainingStructInfo.ByteSize.ToString() + ", " + ContainingStructInfo.NumPointersInSize.ToString() + ")) + " + Offset() + ")";
                }
            }

            if (FetchMethod.IndexOf('(') >= 0)
            {
                ret += ")";
            }

            if (Type == "bool")
            {
                ret = ret + " != 0";
            }

            if (IsEnum)
            {
                ret = "(" + Type + ")" + ret;
            }

            return ret;
        }
    }

    // A struct embedded inside an event is considered its own field within the overall
    // event. The struct knows its entire size and offset, and also has a list of
    // "subfields"--each of which representing a particular field within the struct.
    private class StructInfo : FieldInfo
    {
        public StructInfo(string name) { Name = name; }
        // List of "subfields" (<data> elements in manifest) contained within this <struct>
        public List<FieldInfo> StructSubfieldInfos;
    }

    /* Fields */
    /// <summary>
    /// This is the last component (- separted) of the provider name.  It decides what your TraceParserGen is named
    /// </summary>
    private string m_ClassNamePrefix;
    /// <summary>
    /// All the information from the manifest file.
    /// </summary>
    private Provider m_provider;

    /// <summary>
    /// We group events together by version during the processing.
    /// </summary>
    private SortedDictionary<string, List<Event>> m_eventsByName;

    private static Dictionary<string, string> reservedKeywords = new Dictionary<string, string>(3) { { "object", "Object" }, { "new", "New" }, { "protected", "Protected" } };
    #endregion
}